/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.kie.kogito.addons.quarkus.knative.eventing.deployment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.kie.kogito.codegen.process.events.ProcessCloudEventMetaBuilder;
import org.kie.kogito.event.cloudevents.CloudEventMeta;
import org.kie.kogito.quarkus.extensions.spi.deployment.HasWorkflowExtension;
import org.kie.kogito.quarkus.extensions.spi.deployment.KogitoProcessContainerGeneratorBuildItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem;
import io.quarkus.kubernetes.spi.KubernetesResourceMetadataBuildItem;

import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.mergeList;
import static java.util.stream.Collectors.toSet;

/**
 * Processor to generate cloud event metadata from BPMN/SW models
 */
public class KogitoProcessKnativeEventingProcessor {

    private static final Logger LOGGER = LoggerFactory.getLogger(KogitoProcessKnativeEventingProcessor.class);

    /**
     * Builds the metadata required to create the Kogito Knative resources.
     * <p>
     * It only runs if the current build is not a test environment.
     * This conditional was borrowed from the original `KubernetesProcessor` build processor.
     *
     * @param processContainerBuildItem Build item generated by the kogito-processes-extension with the process context for the generated BPMN/SW files
     * @param extendedCloudEventsBuildItems Build item generated by other extensions that might need to add cloud event definitions for processing
     * @param allDeploymentTargets Build item generated by the quarkus-kubernetes add-on for all the possible deployment targets
     * @param kubernetesMetaBuildItems Build items generated by the quarkus-kubernetes add-on when there's a target deployment resource
     * @param metadataProducer outcome of this build step, it contains the information about cloudevents that should be used by later steps to generate Kogito Knative files
     */
    @BuildStep(onlyIfNot = IsTest.class, onlyIf = HasWorkflowExtension.class)
    void buildMetadata(List<KogitoProcessContainerGeneratorBuildItem> processContainerBuildItem,
            List<KogitoCloudEventsBuildItem> extendedCloudEventsBuildItems,
            List<KubernetesDeploymentTargetBuildItem> allDeploymentTargets,
            List<KubernetesResourceMetadataBuildItem> kubernetesMetaBuildItems,
            BuildProducer<KogitoKnativeResourcesMetadataBuildItem> metadataProducer) {
        if (processContainerBuildItem != null) {
            final Set<CloudEventMeta> cloudEvents =
                    new HashSet<>(this.getCloudEventMetaBuilder().build(processContainerBuildItem.stream().flatMap(it -> it.getProcessContainerGenerators().stream()).collect(toSet())));
            extendedCloudEventsBuildItems.forEach(buildItem -> cloudEvents.addAll(buildItem.getCloudEvents()));
            if (!cloudEvents.isEmpty()) {
                selectDeploymentTarget(allDeploymentTargets, kubernetesMetaBuildItems).map(target -> new KogitoKnativeResourcesMetadataBuildItem(cloudEvents, target))
                        .ifPresentOrElse(metadataProducer::produce, () -> LOGGER.warn("Impossible to get the Kubernetes deployment target for this Kogito service. Skipping generation."));
            }
        }
    }

    // used by unit tests, don't expose
    Optional<KogitoServiceDeploymentTarget> selectDeploymentTarget(
            final List<KubernetesDeploymentTargetBuildItem> allDeploymentTargets,
            final List<KubernetesResourceMetadataBuildItem> kubernetesMetaBuildItems) {
        if (kubernetesMetaBuildItems == null || kubernetesMetaBuildItems.isEmpty()) {
            return Optional.empty();
        }

        // find the target build item ordered by priority
        List<KubernetesDeploymentTargetBuildItem> mergedDeploymentTargets = new ArrayList<>();
        if (allDeploymentTargets != null) {
            mergedDeploymentTargets = mergeList(allDeploymentTargets);
            Collections.sort(mergedDeploymentTargets);
        }

        final Optional<KubernetesDeploymentTargetBuildItem> targetBuildItem =
                mergedDeploymentTargets.stream()
                        .filter(KubernetesDeploymentTargetBuildItem::isEnabled).collect(Collectors.toList())
                        .stream().findFirst();

        // if we can't get the target one, we try with the default available deployment targets
        return kubernetesMetaBuildItems.stream()
                .filter(r -> targetBuildItem.isEmpty() ||
                        (targetBuildItem.get().getName().equals(r.getTarget()) && targetBuildItem.get().getKind().equals(r.getKind())))
                .map(r -> new KogitoServiceDeploymentTarget(r.getGroup(), r.getVersion(), r.getKind(), r.getName()))
                .findFirst();
    }

    // used by unit tests, don't expose
    ProcessCloudEventMetaBuilder getCloudEventMetaBuilder() {
        return new ProcessCloudEventMetaBuilder();
    }
}
